-- PetM
-- Create by weism
-- 管理所有的宠物信息

module("PetM", package.seeall);

-- 所有的宠物信息
local pets = {};

-- 所有宠物模型信息
local petModels = {};

-- 所有皮肤信息
local petSkins = {};

-- 宠物喂食配置
local petFeedTable = {};

-- 宠物食物配置
local petFoodTable = {};

-- 宠物大图标缩放比例和坐标偏移
local petScaleAndPos = {};

local talentOwners = {}

-- 内部函数声明
local whenDoBonus;

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    -- 转换下pet信息表
    local pet_CSV = get_pet_CSV();
    for i = 1, #pet_CSV do
        local classId = pet_CSV[i].class_id;
        pets[classId] = pet_CSV[i];

        -- 解析宠物激活消耗
        pets[classId].activate_cost = parseActivateCostRule(pet_CSV[i].activate_cost);

        -- 解析升级消耗
        pets[classId].upgrade_rank_cost = parseUpgradeRankRule(pet_CSV[i].upgrade_rank_cost);

        -- 解析升级需求
        pets[classId].upgrade_rank_need = parseUpgradeRankRule(pet_CSV[i].upgrade_rank_need);

        -- 天赋ID到冈布奥英雄的映射表
        talentOwners[pet_CSV[i].talent] = pet_CSV[i].class_id;
    end
    pet_CSV = {};

    -- 转换pet模型信息
    local pet_model_CSV = get_pet_model_CSV();
    for i = 1, #pet_model_CSV do
        petModels[pet_model_CSV[i].model_id] = pet_model_CSV[i];

        -- 解析动作帧范围
        parseActionFrameRange(petModels[pet_model_CSV[i].model_id]);
    end
    pet_model_CSV = {};

    -- pet皮肤信息
    local pet_skin_CSV = get_pet_skin_CSV();
    for i = 1, #pet_skin_CSV do
        petSkins[pet_skin_CSV[i].id] = pet_skin_CSV[i];
    end
    pet_skin_CSV = {};

    -- 载入宠物喂食配置表
    local pet_feed_CSV = get_pet_feed_CSV();
    for i = 1, #pet_feed_CSV do
        local classId = pet_feed_CSV[i].class_id;
        petFeedTable[classId] = parseFeedCost(pet_feed_CSV[i]);
    end
    pet_feed_CSV = {};

    -- 载入宠物食物配置表
    local pet_food_CSV = get_pet_food_CSV();
    for i = 1, #pet_food_CSV do
        local classId = pet_food_CSV[i].class_id;
        petFoodTable[classId] = pet_food_CSV[i];
    end
    pet_food_CSV = {};

    -- 载入宠物大图标缩放比例和坐标偏移配置表
    local pet_scale_and_pos_CSV = get_pet_scale_and_pos_CSV();
    for i = 1, #pet_scale_and_pos_CSV do
        local id = pet_scale_and_pos_CSV[i].id;
        petScaleAndPos[id] = pet_scale_and_pos_CSV[i];
    end
    pet_scale_and_pos_CSV = {};
end

-- 模块初始化
function init()
    loadCsv();

    -- 注册登录成功事件
    EventMgr.removeAll("PetM");
    EventMgr.register("PetM", event.LOGIN_OK, function()
        local pets = ME.user.pets;

        for _, pet in pairs(pets) do
            PropM.refresh(pet);
        end
    end);

    -- 注册获得奖励的事件
    EventMgr.register("PetM", event.DO_BONUS, whenDoBonus);
end

-- 解析下激活消耗规则
function parseActivateCostRule(rule)
    local desc = string.explode(rule, "+");
    local ret = {};

    -- 解析
    for index, info in pairs(desc) do
        local arr = string.explode(string.sub(info, 3, -2), ",");
        local type;
        local classId;
        local amount;

        if string.startWith(info, "1(") then
            -- 这是物品奖励
            type = 1
            classId = tonumber(arr[1]);
            amount  = tonumber(arr[2]);
        else
            -- 这是属性
            type = 2;
            classId = arr[1];
            amount  = tonumber(arr[2]);

        end

        -- 奖励信息
        ret[index] = { type, classId, amount, };
    end

    return ret;
end

-- 解析升阶消耗规则
function parseUpgradeRankRule(rule)
    local type;
    local classId;
    local amount;
    local ret = {};
    for rank, cost in pairs(rule) do
        ret[rank] = {};
        local desc = string.explode(cost, "+");
        for _, info in pairs(desc) do
            local arr = string.explode(string.sub(info, 3, -2), ",");
            if string.startWith(info, "1(") then
                -- 这是物品
                type = 1
                classId = tonumber(arr[1]);
                amount  = tonumber(arr[2]);
            elseif string.startWith(info, "2(") then
                -- 这是属性
                type = 2;
                classId = arr[1];
                amount  = tonumber(arr[2]);
            else
                assert(false, "升阶消耗配置格式错误。");
            end

            -- 奖励信息
            table.insert(ret[rank], { type, classId, amount, });
        end
    end

    return ret;
end

-- 解析动作的帧范围
function parseActionFrameRange(info)
    -- 待机、受创、攻击、对自身施法、对他人施法
    local fields = { "idle_frame", "damaged_frame", "attack_frame",
                     "cast_me_frame", "cast_other_frame", "speak_frame"};

    for _, field in pairs(fields) do
        local range = info[field];
        if type(range) == 'string' and string.len(range) > 0 then
            if not string.startWith(range, "[") or not string.endWith(range, "]") then
                error(string.format("字段(%s)配置格式错误!", tostring(field)));
                return info;
            end

            local arr = string.explode(string.sub(range, 2, -2),",");
            if #arr ~= 2 then
                error(string.format("字段(%s)配置格式错误!", tostring(field)));
                return info;
            end

            info[field] = { tonumber(arr[1]), tonumber(arr[2]) };
        end
    end

    return info;
end

-- 解析喂食消耗
function parseFeedCost(row)
    local ret = {};
    for k, v in pairs(row) do
        if type(k) == "string" and string.startWith(k, "lv") then
            -- 多种消耗之间用'+'分隔
            local arr = string.explode(v, "+");

            -- 配置的格式为：'lv%d'
            local level = tonumber(string.sub(k, 3, -1));
            ret[level] = {};
            for _, strCost in pairs(arr) do
                local temp = string.explode(string.sub(strCost, 3, -2), ",");
                if string.startWith(strCost, "1(") then
                    -- 物品消耗
                    table.insert(ret[level], { 1, tonumber(temp[1]), tonumber(temp[2]) });
                elseif string.startWith(strCost, "2(") then
                    -- 属性消耗
                    table.insert(ret[level], { 1, temp[1], tonumber(temp[2]) });
                else
                    assert(false, "暂不支持的格式");
                end
            end
        end
    end

    return ret;
end

-- 检索宠物信息
function query(classId, path)
    local m = pets[classId];
    if (not m or path == "" or path == nil) then
        return m;
    else
        -- 为了配置方便，在配置表中冈布奥只配置纯名称
        -- 在这里把冈布奥的名称拼接起来 "%s冈布奥"
        if path == "name" then
            if isChineseLang() and classId ~= 118 then
                return spliceLocStr(m["name"], getLocStr("slime_full_name"));
            else
                return m["name"];
            end
        -- 当要去获取冈布奥纯名称的时候，直接返回配置表中的"name"
        elseif path == "short_name" then
            return m["name"];
        else
            return m[path];
        end
    end
end

-- 检索模型信息
function queryModel(modelId, path)
    local m = petModels[modelId];
    if (not m or path == "" or path == nil) then
        return m;
    else
        return m[path];
    end
end

-- 检索皮肤信息
function querySkin(skinId, path)
    local m = petSkins[skinId];
    if (not m or path == "" or path == nil) then
        return m;
    else
        return m[path];
    end
end

-- 根据宠物名获得宠物id
function queryPetId(petname)
    for key,value in pairs(pets) do
        if value.name == petname then
            return key;
        end
    end
end

-- 获取默认图标
function getDefaultIcon(classId)
    local modelId = query(classId, "default_model");
    return queryModel(modelId, "icon");
end

-- 获取默认大图标
function getDefaultLargeIcon(classId)
     local modelId = query(classId, "default_model");
    return queryModel(modelId, "large_icon");
end

-----------------------
-- 获取宠物喂食消耗
-- @param classId 宠物编号
-- @param level   当前宠物等级
function getFeedCost(classId, level)
    if not petFeedTable[classId] then
        return nil;
    end

    return petFeedTable[classId][level];
end

------------------------
-- 获取食物的属性
-- @param foodId 食物id
function getFoodProp(foodId)
    if not petFoodTable[foodId] then
        return {};
    end

    return petFoodTable[foodId]["prop"];
end

-- 获得奖励的回调
function whenDoBonus(bonus)
    if bonus[1] ~= 1 then
        -- 不是物品，不处理
        return;
    end

    if not DungeonM.isInDungeon() then
        -- 不在地牢中，不处理
        return;
    end

    local classId = bonus[2];
    local dungeonId = DungeonM.getDungeonId();

    -- 获取该地牢产出的冈布奥食材
    local rawFoods = DungeonAreaM.query(dungeonId, "open_raw_food");
    if type(rawFoods) ~= 'table' or #rawFoods == 0 then
        -- 该地牢不产出食材
        return;
    end

    if table.indexOf(rawFoods, classId) == -1 then
        -- 不是本地牢的食材
        return;
    end

    local activeRawFood = ME.user.dbase:query("active_raw_food", {});
    if table.indexOf(activeRawFood, classId) == -1 then
        -- 标记该食材已激活（可直接购买了）
        table.insert(activeRawFood, classId);
        ME.user.dbase:set("active_raw_food", activeRawFood);
    end
end

-- 判断食材是否激活
function isRawFoodActive(classId)
    local isActive = FormulaM.invoke("IS_FOOD_MATERIAL_ACTIVE", ME.user,
                                     ItemM.query(classId, "rank"));
    return isActive;
--    if ItemM.query(classId, "rank") == 1 then
--        return true;
--    end
--    local activeRawFood = ME.user.dbase:query("active_raw_food", {});
--    if table.indexOf(activeRawFood, classId) ~= -1 then
--        -- 该食材已激活
--        return true;
--    end
--
--    return false;
end

-- 出战宠物
function setActivePet(classId)
    -- 如果没有这只宠物
    if not ME.user.pets[classId] then
        return false
    end

    ME.user.dbase:set("active_pet", classId);

    -- 抛出宠物出战的事件
    EventMgr.fire(event.ACTIVE_PET, classId);

    -- 刷新下玩家的 pet 宠物
    if ME.isInGame then
        PropM.refreshEffect(ME.user, "pet");
    end

    return true;
end

-- 是否当前出战宠物
function isActivePet(classId)
    local activePet = ME.user.dbase:query("active_pet");
    return classId == activePet;
end

-- 是否当前灵魂链接宠物
function isLictorPet(classId)
    local lictorPets = LictorM.getLictorPets();
    for _, c in pairs(lictorPets) do
        if c == classId then
            return true;
        end
    end

    return false;
end

----------------------------------
-- 获取宠物升阶需求
-- @param classId 宠物编号
-- @param rank    宠物当前阶位
function calcUpgradeRankNeed(classId, rank)
    local m = query(classId, "upgrade_rank_need")

    -- 没有该宠物配置
    if not m then
        return;
    end

    -- 该星级的消耗
    return m[rank];
end
----------------------------------
-- 获取宠物升阶消耗
-- @param classId 宠物编号
-- @param rank    宠物当前阶位
function calcUpgradeRankCost(classId, rank)
    -- 现在冈布奥进阶不消耗资源
    do return {}; end

    local m = query(classId, "upgrade_rank_cost")

    -- 没有该宠物配置
    if not m then
        return;
    end

    -- 该星级的消耗
    return m[rank];
end

----------------------------
-- 获取最高级阶级
-- @param pet  宠物对象
function getMaxRank(pet)
    return pet:query("max_rank");
end

----------------------------
-- 是否是满阶级状态
-- @param pet  宠物对象
function isMaxRank(pet)
    if getMaxRank(pet) <= pet:getRank() then
        return true;
    end
    return false;
end

----------------------------
-- 升级是否受限
-- @param rank  该等级是否受限
function isRankLimit(rank)
    local areaId = FormulaM.invoke("GET_UPGRADE_ACTIVE_DUNGEON", rank);
    if areaId < 0 then
        return false;
    end

    if not DungeonAreaM.isDungeonPassed(ME.user, areaId) then
        return true;
    end

    return false;
end

----------------------------
-- 升级是否受限
-- @param pet  宠物对象
function hasRankLimit(pet)
    return isRankLimit(pet:getRank());
end

---------------------------------
-- 能否升阶
-- @param classId 需要升阶的英雄编号
-- @return 如果条件检测通过，则返回ture，否则返回一个错误码
function canUpgradeRank(classId)
    local pet = getMyPet(classId);

    -- 根本没有该宠物
    if not pet then
        return false;
    end

    -- 获取宠物当前阶位
    local rank = pet:getRank();

    -- 该宠物已满阶
    if rank >= pet:query("max_rank") then
        return "rank_upper";
    end

    -- 已经是当前可升的最高阶了（受树洞训练室等级限制）
    if rank >= TrainingM.getLevel() + 1 then
        return "current_max_rank";
    end

        -- 英雄没有穿戴完整装备
    local equipments = pet.equipments;
    if  not equipments or
        not equipments[EQUIP_TYPE_MEDAL] or
        not equipments[EQUIP_TYPE_WEAPON] then
        return "equip_lack";
    end

    local ok = true;

    -- 现在冈布奥进阶不消耗资源
    --[[local cost = calcUpgradeRankCost(classId, rank);
    if not cost then
        error(string.format("无法获取英雄升阶消耗(rank = %s)", tostring(rank)));
        return false;
    end

    for _, arr in pairs(cost) do
        if arr[1] == 1 then
            -- 物品消耗
            ok = ItemM.getAmount(ME.user, arr[2]) >= arr[3];
            if not ok then
            -- 物品数量不足
                return "item_lack";
            end
        else
            -- 属性消耗
            ok = ME.user.dbase:query(arr[2], 0) >= arr[3];
            if not ok then
                -- 属性不足
                return arr[2] .. "_lack";
            end
        end

        -- 有一个条件没通过，下面就不用检查了
        if not ok then
            break;
        end
    end]]

    return ok;
end

-----------------------------
-- 宠物升阶
-- @param classId 需要升阶的宠物编号
-- @return 升阶成功或失败
function upgradeRank(classId)
    -- 先判断是否能升阶
    if true ~= canUpgradeRank(classId) then
        return false;
    end

    local pet  = ME.user.pets[classId];
    local rank = pet.rank;                          -- 宠物阶位
    --[[local cost = calcUpgradeRankCost(classId, rank) -- 消耗

    -- 执行消耗
    for _, arr in pairs(cost) do
        if 1 == arr[1] then
            -- 扣除物品
            if not ItemM.costAmount(ME.user, arr[2], arr[3]) then
                cclog("无法进行宠物升阶，物品扣除失败了。");
                return false;
            end
        elseif 2 == arr[1] then
            -- 扣除属性
            if not ME.user:costAttrib(arr[2], arr[3]) then
                cclog("无法进行宠物升阶，属性扣除失败了。");
                return false;
            end
        else
            assert(false, "消耗配置格式错误。");
        end
    end]]

    -- 武器、徽章干掉
    pet.equipments[EQUIP_TYPE_MEDAL] = nil;
    pet.equipments[EQUIP_TYPE_WEAPON] = nil;

    -- 升阶
    local newRank = rank + 1;
    pet:setRank(newRank);

    -- 重新刷新附加属性
    PropM.refresh(pet);

    TrainingM.setUpgradeEnd();

    -- 天赋增加冈布奥学徒，特殊处理
    if classId == 55 then
        ME.user.dbase:set("idle_worker_num",
                1 + ME.user:queryAttrib("idle_worker_num"));
    end

    -- 宠物升阶事件
    EventMgr.fire(event.UPGRADE_PET_RANK, {["class_id"] = classId, ["rank"] = newRank, });

    EventMgr.fire(event.TRAINING_INFO_UPDATED);
    return true;
end


-----------------------------
-- 升级英雄属性
-- @param classId 需要升阶的英雄编号
-- @param type 升级类型
-- @return 升级成功或失败
function upgradeProp(classId, type)
    -- 类型是否合法
    if type < 1 or type > 4 then
        return false;
    end

    local key = tostring(type);
    local pet = getMyPet(classId);
    if not pet then
        -- 玩家没有该宠物
        return false;
    end

    local upgradeProp = pet:getUpgradeProp();

    -- 是否超过最大升级次数
    local maxTimes = FormulaM.invoke("GET_MAX_PET_PROP_UPGRADE_TIMES");
    local curTimes = pet:getUpgradePropTimes(type);
    if (curTimes >= maxTimes) then
        return "times_limit";
    end

    -- 扣除属性果
    local cost = FormulaM.invoke("GET_PET_PROP_UPGRADE_COST", type, curTimes);
    if not ItemM.costAmount(ME.user, cost[2], cost[3]) then
        return "item_lack";
    end

    -- 增加属性
    local curAddValue = 0;
    if upgradeProp[key] ~= nil and upgradeProp[key]["add_value"] ~= nil then
        curAddValue = upgradeProp[key]["add_value"];
    end

    local val = FormulaM.invoke("GET_PET_PROP_UPGRADE_VALUE", type);
    curAddValue = curAddValue + val;
    curTimes = curTimes + 1;

    if upgradeProp[key] == nil then
        upgradeProp[key] = {};
    end

    upgradeProp[key]["add_value"] = curAddValue;
    upgradeProp[key]["add_times"] = curTimes;

    pet:setUpgradeProp(upgradeProp);

    EventMgr.fire(event.UPGRADE_PET_PROP, type);
    return true;
end

----------------------------
-- 获取最高级别
-- @param pet  宠物对象
function getMaxLevel(pet)
    return pet:query("max_level");
end

----------------------------
-- 获取当前级别
-- @param pet  宠物对象
function getLevel(pet)
    return pet:getLevel();
end

----------------------------
-- 是否是满级状态
-- @param pet  宠物对象
function isMaxLevel(pet)
    if getMaxLevel(pet) <= getLevel(pet) then
        return true;
    end
    return false;
end

-----------------------------
-- 计算宠物升级的消耗
-- @param pet     宠物对象
function calcPetLevelUpCost(pet)
    local nextLevel = pet:getLevel() + 1;
    return FormulaM.invoke("CALC_PET_LEVEL_UP_COST", pet, nextLevel);
end

-- 能否获得一个宠物
function canGainPet(classId)
    -- 没有该宠物配置
    if not pets[classId] then
        return false
    end

    -- 已经拥有该宠物了
    if ME.user.pets[classId] ~= nil then
        return false
    end

    -- 接下来是消耗判断
    local cost = pets[classId].activate_cost
    local arr
    local ok = true
    for _, arr in pairs(cost) do
        if arr[1] == 1 then
            -- 物品消耗
            ok = ItemM.getAmount(ME.user, arr[2]) >= arr[3]
            if not ok then
                return "item_lack";      -- 道具不足
            end
        elseif arr[1] == 2 then
            -- 属性消耗
            ok = ME.user:queryAttrib(arr[2]) >= arr[3]
            if not ok then
                return arr[2] .. "_lack"  -- 属性不足
            end
        else
            ok = false
        end

        -- 有一个条件不符合下面就不再检查了
        if not ok then
            break
        end
    end

    return ok
end

-- 激活（获取）宠物
function gainPet(classId)
    -- 经判断不能激活
    if true ~= canGainPet(classId) then
        return false
    end

    -- 先消耗
    local cost = pets[classId].activate_cost
    local arr
    for _, arr in pairs(cost) do
        if arr[1] == 1 then
            -- 物品消耗
            ItemM.costAmount(ME.user, arr[2], arr[3])
        elseif arr[1] == 2 then
            -- 属性消耗
            local amount = ME.user.dbase:query(arr[2], 0) - arr[3]
            ME.user.dbase:set(arr[2], amount)
        end
    end

    -- 获得宠物
    addPet(classId);

    -- 重新刷新附加属性
    PropM.refresh(ME.user);
end

---------------------------------------
-- 获得一个宠物
-- @param classId   宠物编号
function addPet(classId)
    local pet = getMyPet(classId);
    if pet then
        -- 该宠物已经存在
        return;
    end

    local data = {
        level = query(classId, "init_level"),     -- 等级
        rank  = query(classId, "init_rank"),      -- 阶位
        skill = query(classId, "skill"),          -- 技能
        special =  query(classId, "special"),     -- 专属技能
        passiveSkill = query(classId, "passive"),
    }

    data = table.add(data, query(classId, "dbase") or {});

    local pet = Pet.new(classId, data);
    ME.user.pets[classId] = pet;
    if ME.isInGame then
        PropM.refresh(pet)
        PropM.refreshEffect(ME.user, "skill");
        PropM.refreshEffect(ME.user, "equip");
    end

    -- 天赋增加冈布奥学徒，特殊处理
    if classId == 55 then
        ME.user.dbase:set("idle_worker_num",
                1 + ME.user:queryAttrib("idle_worker_num"));
    end

    -- 临时标记获得的冈布奥
    ME.user.dbase:setTemp("gain_pet_id", classId);

    -- 抛出激活宠物事件
    EventMgr.fire(event.GAIN_PET, classId);
end

-- 是否隐藏
function isHidden(classId)
    -- 玩家未拥有，且get_type为OTHER(未开放)的，隐藏
    return not isActivated(classId) and pets[classId].get_type == OTHER;
end

--获取图鉴宠物列表
function getIllustratePets()
    local list = {}
    for i = 1, #pets do
        if not isHidden(pets[i].class_id) then
            table.insert(list, pets[i].class_id);
        end
    end
    return list;
end

-- 是否已激活
function isActivated(classId)
    return ME.user.pets[classId] ~= nil and pets[classId] ~= nil;
end

-------------------------------
-- 获取玩家的宠物对象（如果玩家没有该宠物，则返回nil）
-- @param classId 宠物编号
function getMyPet(classId)
    return ME.user.pets[classId];
end

-- 获取宠物数量
function getPetNum()
    return #ME.user.pets;
end

-- 获取宠物在玩家宠物列表中的索引
function getPetIndex(classId)
    local pets = ME.user.pets;
    if #pets == 0 then
        return -1;
    end

    local index = 0;
    for _, pet in pairs(pets) do
        index = index + 1;
        if pet.classId == classId then
            return index;
        end
    end

    return -1;
end

--------------------------------------
-- 给英雄佩戴技能
-- @param classId   英雄编号
-- @param skills    需要佩戴的技能id列表
function setSkillOption(classId, skills)
    local pet = getMyPet(classId);

    -- 没有该英雄
    if not pet then
        return false;
    end

    -- 需要扣除专属技能
    local index = table.indexOf(skills, pet.special);
    if index > 0 then
        table.remove(skills, index);
    end

    -- 判断不通过
    if not FormulaM.invoke("CAN_SET_SKILL_OPTION", pet, skills) then
        return false;
    end

    -- 修改选项
    pet:setSkills(skills);

    -- 抛出佩戴技能的事件
    EventMgr.fire(event.SET_SKILL_OPTION);

    return true;
end

---------------------------------
-- 计算英雄解锁的技能格子数
-- @param pet 英雄对象
function getOpenSkillOptionCount(pet)
    return FormulaM.invoke("CALC_OPEN_SKILL_OPTION_COUNT", pet);
end

----------------------------------
-- 获取英雄升阶需要的装备
-- @param pet       英雄对象
-- @param equipType 装备类型
-- @return 如果有可穿戴的装备，则返回之，否则返回一个空的table
function getUpgradeRankEquip(pet, equipType)
    -- 获取宠物当前阶位
    local rank = pet:getRank();

    -- 获取升阶消耗
    local cost = calcUpgradeRankNeed(pet.classId, rank);
    if not cost then
        return {};
    end

    local classId;
    local ret = {};
    for _, arr in pairs(cost) do
        if arr[1] == 1 then
            classId = arr[2];

            -- 找出类型相符的装备
            if equipType == EquipM.query(classId, "type") then
                table.insert(ret, classId);
            end
        end
    end

    return ret;
end

------------------------------
-- 计算英雄重置技能的消耗
-- @param classId  英雄编号
function calcResetSkillCost()
    local cost = FormulaM.invoke("CALC_RESET_SKILL_COST");
    return cost;
end

------------------------------
-- 计算根据天赋技能ID查询冈布奥英雄
-- @param skillId  天赋ID
function getHeroByTalentId(skillId)
    local baseSkill = SkillM.findBaseSkill(skillId);
    return talentOwners[baseSkill];
end

-- 阶位限制的最高等级
function maxLevelByRank(pet, rank)
    local max_level = PetM.getMaxLevel(pet);

    if rank <= 2 then
        return math.min(8, max_level);
    elseif rank <= 3 then
        return math.min(12, max_level);
    elseif rank <= 4 then
        return math.min(16, max_level);
    else
        return max_level;
    end

end

------------------------------
-- 获取喂食上限
-- @param pet  宠物对象
-- @return maxLevel 喂食上限
function getPetMaxLevel(pet)
    -- local rank  = pet:getRank();
    local maxLevel = 8;
    for i = 4, 2, -1 do
        if FeatureItemM.isFeatureOpen("food" .. i) then
            maxLevel = 4 + 4 * i;
            break;
        end
    end

    return maxLevel;
end

------------------------------
-- 判断宠物能否升级
-- @param pet  宠物对象
-- @return 如果可以喂食，返回true，否则返回错码
function canLevelUp(pet, feedId)
    local rank  = pet:getRank();
    local level = pet:getLevel();
    -- 等级还留着，判断不要了
    -- 是否达到指定阶位
    --[[if rank <= 2 and level > 8 then
        return "rank_limit";
    end
    if rank <= 3 and level > 12 then
        return "rank_limit";
    end
    if rank <= 4 and level > 16 then
        return "rank_limit";
    end--]]

    local feedRecord = pet:getFeedRecord();

    if table.indexOf(feedRecord, feedId) ~= -1 then
        -- 已经喂过了
        return false;
    end

    if #feedRecord >= getPetMaxLevel(pet) then
        -- 大于喂食上限
        return "rank_limit";
    end

    local cost = getFeedCost(pet.classId, feedId);
    if not cost then
        -- 获取不到喂食消耗
        return false;
    end

    -- 判断消耗是否足够
    for _, arr in pairs(cost) do
        if arr[1] == 1 then
            -- 物品消耗
            if ItemM.getAmount(ME.user, arr[2]) < arr[3] then
                -- 物品数量不足
                return "item_lack";
            end
        elseif arr[1] == 2 then
            -- 属性消耗
            if ME.user:queryAttrib(arr[2]) < arr[3] then
                -- 属性不足
                return arr[2] .. "_lack";
            end
        end
    end

    return true;
end

------------------------------
-- 宠物升级
-- @param classId  宠物编号
function levelUp(classId, feedId)
    local pet = getMyPet(classId);
    if not pet then
        -- 玩家没有该宠物
        return false;
    end

    -- 经判断无法升级
    if true ~= canLevelUp(pet, feedId) then
        return false;
    end

    -- 扣除消耗
    local cost = getFeedCost(pet.classId, feedId);
    for _, arr in pairs(cost) do
        if arr[1] == 1 then
            -- 物品消耗
            if not ItemM.costAmount(ME.user, arr[2], arr[3]) then
                -- 物品扣除失败
                return false;
            end
        elseif arr[1] == 2 then
            -- 属性消耗
            if not ME.user:costAttrib(arr[2], arr[3]) then
                -- 属性不足
                return false;
            end
        else
            assert(false, "消耗格式错误。");
        end
    end

    -- 升级
    local level    = pet:getLevel();
    local newLevel = level + 1;
    pet:setLevel(newLevel);

    -- 设置喂食点
    local feedRecord = pet:getFeedRecord();
    if table.indexOf(feedRecord, feedId) == -1 then
        table.insert(feedRecord, feedId);
        pet:setFeedRecord(feedRecord);
    end

    -- 尝试解锁技能
    local skills = pet:getSkills();
    local m = query(pet.classId, "original_skills") or {};
    local arr = table.keys(m);
    table.sort(arr);

    local len = #skills;
    for index = len + 1, #arr do
        local level = arr[index];
        local skillId = m[level];
        if newLevel >= level and
            table.indexOf(skills, skillId) <= 0 then
            table.insert(skills, skillId);
        end
    end
    pet:setSkills(skills);

    -- 重新刷新附加属性
    PropM.refresh(pet);

    -- 抛出宠物升级事件
    EventMgr.fire(event.PET_LEVEL_UP, {
        ["class_id"] = classId,
        ["level"]    = newLevel,
        ["pre_level"] = level,
        ["pet"] = pet,
    });

    return true;
end

-- 获取已喂食次数
function getFeedTimes(pet)
    local feedRecord = pet:getFeedRecord() or {};
    return #feedRecord;
end

-------------------------------
-- 获取进阶需要的徽章
-- @param classId  英雄id
-- @param rank     英雄阶位（如果没有传入，则默认取当前阶位）
function getMedal(classId, rank)
    local pet = getMyPet(classId);

    if not rank then
        if pet then
            rank = pet:getRank();
        else
            return nil;
        end
    end

    local need = calcUpgradeRankNeed(classId, rank);
    if not need then
        -- 消耗不存在，可能是没有配置
        return nil;
    end

    for _, v in pairs(need) do
        local medalId = v[2];
        if ItemM.query(medalId, "group") == 13 then
            return medalId;
        end
    end

    return nil;
end

-------------------------------
-- 获取下一级徽章
-- @param classId  英雄id
-- @param rank     英雄阶位（如果没有传入，则默认取当前阶位）
function getNextMedal(classId)
    local pet = getMyPet(classId);

    if not pet then
        return nil;
    end
    local rank = pet:getRank();

    -- 是否已经佩戴了该徽章
    local hasMedal = EquipM.getHeroEquip(pet, EQUIP_TYPE_MEDAL, 1);

    if hasMedal then
        return getMedal(classId, rank + 1);
    else
        return getMedal(classId, rank);
    end
end

-------------------------------
-- 获取进阶需要的武器
-- @param classId  英雄id
-- @param rank     英雄阶位（如果没有传入，则默认取当前阶位）
function getWeapon(classId, rank)
    local pet = getMyPet(classId);
    if not pet then
        -- 玩家没有该英雄
        return nil;
    end

    if not rank then
        rank = pet:getRank();
    end

    local need = calcUpgradeRankNeed(classId, rank);
    if not need then
        -- 消耗不存在，可能是没有配置
        return nil;
    end

    for _, v in pairs(need) do
        local weaponId = v[2];
        if ItemM.query(weaponId, "group") == 12 then
            return weaponId;
        end
    end

    return nil;
end

-------------------------------
-- 获取喂食所需的食物
-- @param classId  英雄id
-- @param level    英雄等级（如果没有传入，则默认取当前等级）
function getFeedFood(classId, level)
    local pet = getMyPet(classId);
    if not pet then
        -- 玩家没有该英雄
        return nil;
    end

    if not level then
        level = pet:getLevel();
    end

    local cost = getFeedCost(classId, level);
    if not cost then
        -- 消耗不存在，可能是没有配置
        return nil;
    end

    -- 默认返回第一个消耗（目前仅一种消耗）
    return cost[1];
end

------------------------------
-- 判断喂食所需的食物是否足够
-- @param classId 英雄id
function isFoodEnough(classId, feedId)
    local food = getFeedFood(classId, feedId);
    if not food then
        -- 获取不到食物配置
        return false;
    end

    if ItemM.getAmount(ME.user, food[2]) < food[3] then
        return false;
    end

    return true;
end

--------------------------------
-- 判断合成食物所需的材料是否足够
-- @param classId 英雄id
-- @param checkType 检查类型 目前有 徽章 medal 武器 weapon 食物 food
function isMaterialEnough(classId, checkType, feedId)
    local materialId;

    if checkType == "medal" then
        materialId = getMedal(classId);
    elseif checkType == "weapon" then
        materialId = getWeapon(classId);
    else
        local food = getFeedFood(classId, feedId);
        if not food then
            -- 获取不到食物配置
            return false;
        end

        materialId = food[2];
    end

    if not materialId then
        return false;
    end

    -- 获取合成规则
    local rule = CompoundM.getCompoundRule(materialId);
    if not rule then
        return false;
    end

    -- 判断能否合成
    return CompoundM.canDoCompound(rule);
end

---------------------------------
-- 合成食物
-- @param classId 英雄id
function compoundFood(classId, feedId)
    if isMaterialEnough(classId, "food", feedId) ~= true then
        -- 材料不足
        return false;
    end

    local food = getFeedFood(classId, feedId);
    local foodId = food[2];

    -- 获取合成规则
    local rule = CompoundM.getCompoundRule(foodId);
    if not rule then
        return false;
    end

    -- 合成
    Operation.cmd_do_compound(rule);

    return true;
end

------------------------
-- 计算喂食带来的属性效果
-- @param pet  英雄对象
function fetchFeedProps(pet)
    local feedRecord = pet:getFeedRecord();

    if #feedRecord <= 0 then
        -- 从未喂食过
        return {};
    end

    -- 将每一次喂食的属性效果叠加起来
    local ret = {};
    local classId = pet.classId;
    for _, feedId in pairs(feedRecord) do
        -- 获取该等级时喂食需要的食物
        local food = getFeedFood(classId, feedId);
        if type(food) == 'table' and #food > 0 then
            local foodId = food[2];
            local prop = petFoodTable[foodId]["prop"];
            if type(prop) == 'table' and #prop > 0 then
                ret = table.append(ret, prop);
            end
        end
    end

    return ret;
end

-- TODO: 获取已经被吃掉的装备属性
function fetchOldEquipProps(pet)
    local rank = pet:getRank();
    local props = {};
    local equips = {};

    for i = 1, rank - 1 do
        local cost = calcUpgradeRankNeed(pet.classId, i) or {};
        for _, arr in pairs(cost) do
            table.insert(equips, arr[2]);
        end
    end

    -- 收集装备属性
    for _, classId in pairs(equips) do
        local propList = EquipM.calcBaseProp(pet, classId);
        for _, prop in pairs(propList) do
            table.insert(props, prop);
        end
    end

    return props;
end

-------------------------------
-- 判断能否购买食材
-- @param classId   食材id
-- @param count     购买数量
function canBuyRawFood(classId, count)
    if not FormulaM.invoke("CAN_BUY_RAW_FOOD", classId) then
        -- 该食材无法通过购买获得
        return "forbid_buy";
    end

    if not PetM.isRawFoodActive(classId) then
        -- 该食材尚未激活
        return "not_active";
    end

    local buyPrice = ItemM.query(classId, "buy_price");
    if not buyPrice then
        trace("PetM", "尚未配置食材(%d)的购买价格，无法购买。", classId);
        return false;
    end

    local price = buyPrice[1];
    if ME.user:queryAttrib(price[2]) < count * price[3] then
        -- 金钱不足
        return price[2] .. "_lack";
    end

    return true;
end

-------------------------------
-- 购买食材
-- @param classId   食材id
-- @param count     购买数量
function buyRawFood(classId, count)
    if true ~= canBuyRawFood(classId, count) then
       -- 不符合购买条件
       return false;
    end

    local buyPrice = ItemM.query(classId, "buy_price");
    local price = buyPrice[1];

    -- 扣除属性
    if not ME.user:costAttrib(price[2], count * price[3]) then
        -- 属性扣除失败
        return false;
    end

    -- 执行奖励
    BonusM.doBonus( { 1, classId, count } );

    EventMgr.fire(event.BUY_RAW_FOOD, { ["classId"] = classId, ["count"] = count, });

    return true;
end

-- 获取最多可喂食的次数
function getMaxFeedTimes()
    return 20;  -- 暂时写死
end

-- 获取英雄可升到的最高阶位
function getMaxUpgradeRank()
    local maxRank = 1;
    while (not isRankLimit(maxRank)) do
        maxRank = maxRank + 1;
    end

    return maxRank;
end

-- 当前阶级最大喂食数
function getMaxFeedId(pet)
    -- local rank  = pet:getRank();
    local maxFeedId = 8;
    for i = 4, 2, -1 do
        if FeatureItemM.isFeatureOpen("food" .. i) then
            maxFeedId = 4 + 4 * i;
            break;
        end
    end

    return maxFeedId;
end

-- 判断能否有可以喂食的操作
function canFeed(petId)
    local pet = getMyPet(petId);
    local feedRecord = pet:getFeedRecord();
    local maxFeedId = getMaxFeedId(pet);
    local result = false;

    for i = 1, maxFeedId do
        if table.indexOf(feedRecord, i) == -1 and
            canOperate(petId, "food", i) == true then
            result = true;

            break;
        end
    end

    return result;
end

-- 判断能否进行相关操作
function canOperate(petId, checkType, feedId)
    local pet = getMyPet(petId);

    if checkType == "upgrade" then
        -- 能否提示升阶
        if canUpgradeRank(petId) ~= true then
            return false;
        end
    elseif checkType == "medal" then
        -- 能否提示佩戴徽章
        -- 判断当前拥有的徽章碎片是否满足合成需求
        -- 获取已经佩戴的徽章，如果已经佩戴，则不提示
        local hasMedal = EquipM.getHeroEquip(pet, EQUIP_TYPE_MEDAL, 1);

        if hasMedal then
            return false;
        end

        if isMaterialEnough(petId, "medal") ~= true then
            return false;
        end
    elseif checkType == "weapon" then
        -- 能否提示佩戴武器
        -- 判断当前拥有的材料是否满足合成需求
        -- 获取已经佩戴的武器，如果已经佩戴，则不提示
        local hasWeapon = EquipM.getHeroEquip(pet, EQUIP_TYPE_WEAPON, 1);

        if hasWeapon then
            return false;
        end

        if isMaterialEnough(petId, "weapon") ~= true or hasWeapon then
            return false;
        end
    else
        if not getFeedFood(petId, feedId) then
            -- 获取不到食物配置（可能已经满级）
            return false;
        end

        if not FeatureItemM.isFeatureOpen("food") then
            -- 喂食功能未激活，不允许喂食
            return false;
        end

        if isFoodEnough(petId, feedId) ~= true and isMaterialEnough(petId, "food", feedId) ~= true then
            -- 食物和食材都不足
            return false;
        end
    end

    return true;
end

-- 获取宠物大图标缩放比例
function getPetScaleAndPos(petId, type)
    if not petScaleAndPos[petId] then
        return {["scale"] = 1, ["offset_x"] = 0, ["offset_y"] = 0, };
    else
        if type == "detail" then
            return {["scale"] = petScaleAndPos[petId].detail_scale, ["offset_x"] = petScaleAndPos[petId].detail_x, ["offset_y"] = petScaleAndPos[petId].detail_y };
        else
            return {["scale"] = petScaleAndPos[petId].main_scale, ["offset_x"] = petScaleAndPos[petId].main_x, ["offset_y"] = petScaleAndPos[petId].main_y };
        end
    end
end

-- 获取可喂食的英雄列表
function getHeroListCanFeedOrEquip()
    local ret = {};

    local heroList = ME.user:getAllPetIds();
    for _, id in pairs(heroList) do
        local pet = PetM.getMyPet(id);
        local weaponClassId = EquipM.getHeroEquip(pet, EQUIP_TYPE_WEAPON, 1);
        local medalClassId = EquipM.getHeroEquip(pet, EQUIP_TYPE_MEDAL, 1);

        if canFeed(id) == true or (not weaponClassId and isMaterialEnough(id, "weapon") == true) or (not medalClassId and isMaterialEnough(id, "medal") == true) then
            table.insert(ret, id);
        end
    end

    return ret;
end

-- 是否是收魂者冈布奥
function isReviveSlime(heroId)
    local dbase = PetM.query(heroId, "dbase");
    if not dbase then
        return false;
    end

    return 1 == dbase["revive_slime"];
end

-- 根据碎片id获取冈布奥id
function getPetidByPieceid(classId)
    local petId = classId;
    local map = ItemM.query(classId, "desc_args");

    -- 暂时用描述参数来获取
    if type(map) == "table" and
       type(map["pet_id"]) == "number" then
        petId = map["pet_id"];
    end

    return petId;
end

-- 获取某类获取途径的所有冈布奥/碎片
function getTypeSlimeList(getType, isPiece)
    local i;
    local list = { };
    for i = 1, #pets do
        if pets[i].get_type == getType then
            if isPiece then
                table.insert(list, 1, pets[i].chips);
            else
                table.insert(list, 1, i);
            end
        end
    end

    return list;
end

-- 获取某个阵营的所有稀有冈布奥
function getTypeSlimeListByCamp(campId, getType)
    local list = getTypeSlimeList(getType, false);
    local ret = {};

    for _, pet_id in pairs(list) do
        local petCamp = CampM.getPetCamp(pet_id);
        if petCamp == -1 then
            -- 设置成默认
            petCamp = 1;
        end
        if petCamp == campId then
            table.insert(ret, pet_id);
        end
    end
    return ret;
end

-- 随机获取一只某个阵营没有被移出黄金罐的冈布奥
function getMerCampCanShowSlimeByCamp(campId)
    local ret = 0;
    local infos = getTypeSlimeListByCamp(campId, RARE);
    table.randomSort(infos);
    local rareSlimeSet = ME.user.dbase:query("rare_slime_setting") or {};
    for _, pet_id in pairs(infos) do
        if not rareSlimeSet[pet_id] or rareSlimeSet[pet_id] ~= 1 then
            ret = pet_id;
            break;
        end
    end
    return ret;
end

-- 获取黄金罐包含的某阵营冈布奥
function getFetchSlimeList(campId, getType, isPiece)
    local ret = {};
    local infos = getTypeSlimeListByCamp(campId, getType);
    infos = table.append(infos, getActivitySlimeListByCamp(campId));
    local rareSlimeSet = ME.user.dbase:query("rare_slime_setting") or {};
    for _, pet_id in pairs(infos) do
        if not rareSlimeSet[pet_id] or rareSlimeSet[pet_id] ~= 1 then
            if isPiece then
                table.insert(ret, 1, pets[pet_id].chips);
            else
                table.insert(ret, 1, pet_id);
            end
        end
    end
    return ret;
end

-- 获取迷宫内英雄列表
function getDungeonPetList()
    local list = {};
    local activePet = ME.user.dbase:query("active_pet");

    table.insert(list, activePet);

    local lictorPet = ME.user.dbase:query("lictor_pets") or {};

    for _, petId in ipairs(lictorPet) do
        if LictorM.canDispatch(petId) then
            table.insert(list, petId);
        end
    end

    return list;
end

-- 判断是否特殊冈布奥
--(特殊冈布奥迷宫内，能量条颜色不一样)
function isSpecialPet(id)
    return id == 24 or id == 25 or id == 35;
end

-- 获取特殊冈布奥对应attrib
function getAttribForSpecialPet(id)
    local attribList = { [24] = "soul_collect", [25] = "vital_essence", [35] = "opti_point" };
    return attribList[id];
end

-- 打包下新增的宠物
function packAddPets(filter)
    local ret = {};
    local pets = ME.user.pets or {};

    for classId, _ in pairs(pets) do
        if not filter[classId] then
            table.insert(ret, classId);
        end
    end

    return ret;
end

-- 参战宠物
function isFighting(classId)
    if classId == ME.user.dbase:query("active_pet") then
        return true;
    end

    if table.indexOf(ME.user.dbase:query("lictor_pets", {}), classId) > 0 then
        return true;
    end

    return false;
end

-- 获取宠物升阶提高的属性
function getPetUpRankAddAttrib(classId, rank, attrib)
    local props = {};
    local equips = {};
    local value = 0;

    -- 目前只有对应阶位装备的附加属性
    local cost = calcUpgradeRankNeed(classId, rank - 1) or {};
    for _, arr in pairs(cost) do
        table.insert(equips, arr[2]);
    end

    -- 统计对应的属性
    for _, classId in pairs(equips) do
        local propList = EquipM.calcBaseProp(ME.user, classId);
        for _, prop in pairs(propList) do
            if PropM.getPropId("attrib2") == prop[1] and attrib == prop[2] then
                value = value + prop[3];
            end
        end
    end

    return value;
end

-- 获取宠物初始阶位的属性
function getPetInitRankAttrib(classId)
    -- 宠物的初始阶位
    local rank = query(classId, "init_rank");

    local   ret = {
        ["attack"] = query(classId, "attack"),
        ["magic"]  = query(classId, "magic"),
        ["max_hp"] = query(classId, "hp"),
        ["max_mp"] = query(classId, "mp"),
    };

    -- 低阶装备的附加属性
    local equips = {};
    for i = 1, rank - 1 do
        local cost = calcUpgradeRankNeed(classId, i) or {};
        for _, arr in pairs(cost) do
            table.insert(equips, arr[2]);
        end
    end

    -- 统计属性
    local arr = table.keys(ret);
    for _, classId in pairs(equips) do
        local propList = EquipM.calcBaseProp(ME.user, classId);
        for _, prop in pairs(propList) do
            if PropM.getPropId("attrib2") == prop[1] and table.indexOf(arr, prop[2]) > 0 then
                ret[prop[2]] = ret[prop[2]] + prop[3];
            end
        end
    end

    return ret;
end

-- 暂离限制宠物行为（喂食/升阶/装备）
function pauseDungeonLimit(classId)
    if (PetM.isLictorPet(classId) or isActivePet(classId))
        and DungeonM.isPause() then
        return true;
    end

    return false;
end

-- 喂食史莱姆
function compoundAndFeedPet(petId, feedId)
    local pet = getMyPet(petId);
    if not pet then
        -- 玩家没有该宠物
        return false;
    end

    -- 当前已经喂食过了不再喂食
    local feedRecord = pet:getFeedRecord();

    if table.indexOf(feedRecord, feedId) ~= -1 then
        -- 已经喂过了
        return false;
    end

    if #feedRecord >= getPetMaxLevel(pet) then
        -- 大于喂食上限
        return false;
    end

    -- 食物足够直接喂食
    if isFoodEnough(petId, feedId)  then
        if levelUp(petId, feedId) == true then
            return true;
        else
            return false;
        end

    else
        local ret = PetM.isMaterialEnough(petId, "food", feedId);

        -- 食物不足
        if ret ~= true then
            return false;
        end

        local food = getFeedFood(petId, feedId);
        local foodId = food[2];

        -- 获取合成规则
        local rule = CompoundM.getCompoundRule(foodId);
        if not rule then
            return false;
        end

        -- 先合成(合成不成功什么也不做)
        if CompoundM.doCompound(rule, "compound") ~= true then
            return false;
        end

        -- 直接尝试升级,合成后食物默认足够的
        levelUp(petId, feedId);
    end

    return true;
end

-- 获取某个阵营的所有活动冈布奥
function getActivitySlimeListByCamp(campId)
    local list = getTypeSlimeList(ACTIVITY, false);
    local ret = {};

    for _, pet_id in pairs(list) do
        local petCamp = CampM.getPetCamp(pet_id);
        if petCamp == -1 then
            -- 设置成默认
            petCamp = 1;
        end

        local dbase = query(pet_id, "dbase") or {};
        local activityId = dbase["activity_id"] or 0;
        if ActivityM.hasActivityOpenOnce(activityId) and petCamp == campId then
            table.insert(ret, pet_id);
        end
    end

    return ret;
end

-- 获取自己的宠物图标
function getIcon(classId)
    local fileName = getDefaultIcon(classId);
    local pet = PetM.getMyPet(classId);
    if pet then
        fileName = pet:getIcon();
    end
    return fileName;
end